package org.xx.armory.swing.components;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.commons.AbstractLifeCycle;
import org.xx.armory.commons.LifeCycle;
import org.xx.armory.swing.DialogUtils;

import javax.swing.*;
import java.awt.Dimension;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.xx.armory.commons.Validators.notNull;
import static org.xx.armory.commons.Validators.validIndex;
import static org.xx.armory.swing.UIUtils.UPDATE_UI;
import static org.xx.armory.swing.UIUtils.assertUIThread;

/**
 * 进度条对话框。
 */
public class ProgressDialog
        extends DialogPane {
    private final Logger logger = LoggerFactory.getLogger(ProgressDialog.class);
    private final ExecutorService executor;
    private final Object taskLocker;
    private final BoundedRangeModel rangeModel;
    private ProgressRunner<?> runner;
    private TaskStatus taskStatus;
    private int success;
    private int fail;

    /**
     * 构造进度条对话框。
     *
     * @param runner
     *         指定的任务执行器。
     */
    public ProgressDialog(
            ProgressRunner<?> runner
    ) {
        this.executor = Executors.newSingleThreadExecutor();
        this.runner = notNull(runner, "runner");
        this.rangeModel = new DefaultBoundedRangeModel(0, 0, 0, 100);
        this.taskLocker = new Object();
        this.taskStatus = TaskStatus.UNINITIALIZED;
        this.success = 0;
        this.fail = 0;

        setTitle(runner.getTitle());

        final JProgressBar progress = controller().get(JProgressBar.class, "progress");
        progress.setStringPainted(true);
        progress.setBorderPainted(false);
        final Dimension progressPreferredSize = progress.getPreferredSize();
        progressPreferredSize.width = 540;
        progress.setPreferredSize(progressPreferredSize);
        progress.setModel(this.rangeModel);

        controller().connect("suspend", evt -> this.suspend());
        controller().connect("resume", evt -> this.resume());
        controller().connect("cancel", evt -> this.cancel());

        controller().disable("suspend");
        controller().disable("resume");
    }

    /**
     * 启动指定的任务执行器。
     *
     * @throws IllegalStateException
     *         如果当前的对话框状态不是未初始化状态、取消态或者完成态。
     */
    public void start() {
        synchronized (this.taskLocker) {
            switch (this.taskStatus) {
                case UNINITIALIZED:
                    // 未初始化态，继续初始化。
                    break;
                case DONE:
                case CANCELLED:
                    // 完成态和取消态，直接返回。
                    return;
                default:
                    // 其它状态都是非法的。
                    throw new IllegalStateException("illegal task status: " + this.taskStatus);
            }

            CompletableFuture
                    .runAsync(this::updateRunnerInitializing, UPDATE_UI)
                    .thenAcceptAsync(dummy -> this.initializeRunner(), this.executor)
                    .thenAcceptAsync(dummy -> this.updateRunnerInitialized(), UPDATE_UI)
                    .whenCompleteAsync((dummy, throwable) -> this.runnerInitializedCallback(throwable), UPDATE_UI);

            this.taskStatus = TaskStatus.INITIALIZING;
            this.taskLocker.notifyAll();
        }
    }

    /**
     * 初始化任务执行器。
     *
     * @throws IllegalStateException
     *         如果初始化执行器时出现错误。
     */
    private void initializeRunner() {
        try {
            this.runner.initialize();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    /**
     * 任务执行器被初始化之后的回调。
     *
     * @param throwable
     *         初始化任务执行器时出现错误。
     */
    private void runnerInitializedCallback(
            Throwable throwable
    ) {
        if (throwable != null) {
            DialogUtils.fail(getOwner(), throwable.getMessage());
            return;
        }

        synchronized (this.taskLocker) {
            this.success = 0;
            this.fail = 0;

            CompletableFuture<Void> task = CompletableFuture.completedFuture(null);

            for (int i = 0; i < runner.getSize(); ++i) {
                int index = i;
                task = task
                        .thenAcceptAsync(dummy -> this.updateRunnerStepRunning(index), UPDATE_UI)
                        .thenAcceptAsync(dummy -> this.executeRunnerStep(index), this.executor)
                        .thenAcceptAsync(dummy -> this.updateRunnerStepRun(index), UPDATE_UI);
            }

            task.thenAcceptAsync(dummy -> this.completeRunner(), UPDATE_UI);

            this.taskStatus = TaskStatus.RUNNING;
            this.taskLocker.notifyAll();
        }
    }

    /**
     * 执行指定的任务步骤。
     *
     * @param index
     *         任务步骤序号，从0开始。
     * @throws IllegalArgumentException
     *         如果参数{@code index}小于{@code 0}或者大于最大步骤序号。
     */
    private void executeRunnerStep(
            int index
    ) {
        synchronized (this.taskLocker) {
            // 每个步骤开始执行前，检查执行状态。
            if (waitRunningSignal()) {
                try {
                    this.runner.execute(index);
                    ++this.success;
                } catch (Exception ex) {
                    ++this.fail;
                }

                if (index >= this.runner.getSize() - 1) {
                    this.taskStatus = TaskStatus.DONE;
                    this.taskLocker.notifyAll();
                }
            }
        }
    }

    /**
     * 结束任务执行器。
     */
    private void completeRunner() {
        synchronized (this.taskLocker) {
            switch (this.taskStatus) {
                case DONE:
                    done(OK);
                    break;
                default:
                    done(CANCEL);
                    break;
            }
        }
    }

    /**
     * 关闭任务执行器。
     */
    private void closeRunner() {
        try {
            runner.close();
        } catch (Exception ex) {
            logger.warn("cannot close progress runner", ex);
        }
    }

    /**
     * 更新任务执行器正在初始化的界面内容。
     */
    private void updateRunnerInitializing() {
        // 正在初始化，显示初始化消息。
        controller().setText("current-status", controller().getMessage("initializing"));
        controller().hide("overview-status");
        this.rangeModel.setValue(0);

        updateButtons();

        pack();
    }

    /**
     * 更新任务执行器初始化完成的界面内容。
     */
    private void updateRunnerInitialized() {
        // 初始化完成，显示初始化消息。
        controller().setText("current-status", "");
        controller().show("overview-status");
        controller().setText("overview-status", controller().formatMessage("overview", this.success, this.fail));
        this.rangeModel.setValue(0);

        updateButtons();

        pack();
    }

    /**
     * 更新任务执行器正在执行步骤的界面内容。
     *
     * @param index
     *         任务步骤序号，从0开始。
     * @throws IllegalArgumentException
     *         如果参数{@code index}小于{@code 0}或者大于最大步骤序号。
     */
    private void updateRunnerStepRunning(
            int index
    ) {
        assertUIThread();

        validIndex(index, "index", this.runner.getSize() - 1);

        // 准备执行当前步骤，显示当前步骤描述。
        controller().setText("current-status", runner.getCurrent(index));

        pack();
    }

    /**
     * 更新任务执行器执行步骤结果的界面内容。
     *
     * @param index
     *         任务步骤序号，从0开始，如果此序号等于步骤总数，说明所有步骤都已执行（可能部分或者全部步骤失败）。
     * @throws IllegalArgumentException
     *         如果参数{@code index}小于{@code 0}或者大于最大步骤序号。
     */
    private void updateRunnerStepRun(
            int index
    ) {
        assertUIThread();

        final int total = this.runner.getSize();
        final int finished = this.success + this.fail;
        validIndex(index, "index", total - 1);

        // 当前步骤执行完毕，显示全部执行情况概述和进度。
        controller().show("overview-status");
        controller().setText("overview-status", controller().formatMessage("overview", total, this.success, this.fail));
        this.rangeModel.setValue(finished * 100 / total);

        updateButtons();

        pack();
    }

    private void updateButtons() {
        synchronized (this.taskLocker) {
            switch (this.taskStatus) {
                case UNINITIALIZED:
                    controller().disable("suspend");
                    controller().disable("resume");
                    controller().enable("cancel");
                    break;
                case INITIALIZING:
                    controller().disable("suspend");
                    controller().disable("resume");
                    controller().enable("cancel");
                    break;
                case RUNNING:
                    controller().disable("resume");
                    controller().enable("suspend");
                    controller().enable("cancel");
                    setTitle(this.runner.getTitle());
                    break;
                case WAITING:
                    controller().disable("suspend");
                    controller().enable("resume");
                    controller().enable("cancel");
                    setTitle(this.runner.getTitle() + controller().getMessage("suspended-suffix"));
                    break;
                case DONE:
                    controller().disable("suspend");
                    controller().disable("resume");
                    controller().disable("cancel");
                    break;
                case CANCELLED:
                    controller().disable("suspend");
                    controller().disable("resume");
                    controller().disable("cancel");
                    break;
                default:
            }
        }
    }

    /**
     * 检查任务是否应当继续执行。
     * <ul>
     * <li>如果是等待态则等待信号触发并翻转到执行态，返回{@code true}。</li>
     * <li>如果是执行态则继续执行，返回{@code true}。</li>
     * <li>如果是取消态则返回{@code false}。</li>
     * <li>如果线程被中断也返回{@code false}。</li>
     * <li>如果出现了其它状态则抛出状态错误异常。</li>
     * </ul>
     *
     * @throws IllegalStateException
     *         如果对话框状态不合法。
     */
    private boolean waitRunningSignal() {
        if (Thread.interrupted()) {
            // 线程已被中断，停止执行。
            return false;
        }

        while (this.taskStatus == TaskStatus.WAITING) {
            try {
                this.taskLocker.wait();
            } catch (InterruptedException ignore) {
                // 等待信号时线程被中断，停止执行。
                return false;
            }
        }

        switch (this.taskStatus) {
            case CANCELLED:
                // 任务被取消，停止执行。
                return false;
            case RUNNING:
                // 进入运行状态，继续执行。
                return true;
            default:
                // 其它状态都是不合法的。
                throw new IllegalStateException("illegal task status: " + this.taskStatus);
        }
    }

    /**
     * 暂停执行，如果当前对话框不是运行态则不执行任何操作。
     */
    public final void suspend() {
        synchronized (this.taskLocker) {
            switch (this.taskStatus) {
                case RUNNING:
                    this.taskStatus = TaskStatus.WAITING;
                    this.taskLocker.notifyAll();
                default:
            }
        }
    }

    /**
     * 恢复执行，如果当前对话框不是等待态则不执行任何操作。
     */
    public final void resume() {
        synchronized (this.taskLocker) {
            switch (this.taskStatus) {
                case WAITING:
                    this.taskStatus = TaskStatus.RUNNING;
                    this.taskLocker.notifyAll();
                default:
            }
        }
    }

    /**
     * 取消执行，如果当前对话框已处于完成态或者取消态则不执行任何操作。
     */
    public final void cancel() {
        synchronized (this.taskLocker) {
            switch (this.taskStatus) {
                case UNINITIALIZED:
                case INITIALIZING:
                case RUNNING:
                case WAITING:
                    this.taskStatus = TaskStatus.CANCELLED;
                    this.taskLocker.notifyAll();
                default:
            }
        }
    }

    /**
     * 判断进度对话框是否可以被关闭。
     *
     * @return 进度对话框是否可以被关闭。
     */
    public final boolean canClose() {
        synchronized (this.taskLocker) {
            switch (this.taskStatus) {
                case DONE:
                case CANCELLED:
                    return true;
                default:
                    return false;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void windowOpened(
            WindowEvent event
    ) {
        super.windowOpened(event);

        // 如果允许自动开始执行任务，那么开始执行。
        start();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void windowClosing(
            WindowEvent event
    ) {
        if (canClose()) {
            closeRunner();
            super.windowClosing(event);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void windowClosed(
            WindowEvent event
    ) {
        super.windowClosed(event);

        // 关闭执行器。
        this.executor.shutdownNow();
    }

    /**
     * 任务执行状态。
     */
    private enum TaskStatus {
        /**
         * 任务尚未初始化，不可用，可以被直接取消。
         */
        UNINITIALIZED,
        /**
         * 任务正在初始化中，不可用，可以被直接取消。
         */
        INITIALIZING,
        /**
         * 任务已初始化，并在执行中，可以被暂停或者取消。
         */
        RUNNING,
        /**
         * 任务被暂停，可以被恢复或者取消。
         */
        WAITING,
        /**
         * 任务已完成，无法变更状态。
         */
        DONE,
        /**
         * 任务已被取消，无法变更状态。
         */
        CANCELLED
    }

    /**
     * 任务执行器，由若干个任务步骤组成。可以不包含任何任务步骤。
     */
    public static abstract class ProgressRunner<T>
            extends AbstractLifeCycle
            implements LifeCycle {
        private List<T> items;

        protected ProgressRunner() {
            this.items = new ArrayList<>();
        }

        /**
         * 获取步骤的数量。
         *
         * @return 步骤的数量。
         */
        public final int getSize() {
            return this.items.size();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void initialize()
                throws Exception {
            super.initialize();

            final Collection<T> items = load();
            if (items != null) {
                this.items.addAll(items);
            }
        }

        public abstract String getTitle();

        /**
         * 获取表示执行步骤的文本。
         *
         * @param index
         *         步骤序号，从0开始。
         * @return 表示执行步骤的文本。
         * @throws IllegalStateException
         *         如果当前任务执行器尚未被初始化。
         */
        public final String getCurrent(
                int index
        ) {
            assertInitialized();

            return getCurrent(index, this.items.get(index));
        }

        /**
         * 执行当前步骤。
         *
         * @param index
         *         步骤序号。
         * @throws IllegalStateException
         *         如果当前任务执行器尚未被初始化。
         * @throws IndexOutOfBoundsException
         *         如果步骤序号超过合理值。
         * @throws Exception
         *         如果执行步骤时出现错误。
         */
        public final void execute(
                int index
        )
                throws Exception {
            assertInitialized();

            execute(index, this.items.get(index));
        }

        /**
         * 加载所有待执行的步骤。
         *
         * @return 所有待执行的步骤。
         */
        protected abstract Collection<T> load();

        /**
         * 获取表示执行步骤的文本。
         *
         * @param index
         *         步骤序号，从0开始。
         * @param item
         *         步骤的值。
         * @return 表示执行步骤的文本。
         */
        protected abstract String getCurrent(
                int index,
                T item
        );

        /**
         * 执行指定序号的步骤。
         *
         * @param index
         *         步骤序号，从0开始。
         * @param item
         *         步骤的值。
         * @throws Exception
         *         如果执行指定的步骤时出现错误。
         */
        protected abstract void execute(
                int index,
                T item
        )
                throws Exception;
    }
}
